// SPDX-FileCopyrightText: Copyright 2025-2025 深圳市同心圆网络有限公司
// SPDX-License-Identifier: GPL-3.0-only

package service_impl

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/url"
	"strings"
	"time"

	"gitcode.com/openseaotter/so_proto_gen_go.git/watch_api"
	"gitcode.com/openseaotter/so_server/config"
	"gitcode.com/openseaotter/so_server/storage"
	"gitcode.com/openseaotter/so_server/storage/core"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

func ProcessStatManifest(c *gin.Context) {
	groupName := c.Param("group")
	imageName := c.Param("name")
	imageRef := c.Param("ref")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}

	//检查用户是否可以pull
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PullSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow pull")
		return
	}

	refItem, err := storage.ImageReferenceDao.Get(c.Request.Context(), groupName, imageName, imageRef)
	if err != nil {
		SendError(c, 404, ERROR_CODE_NAME_UNKNOWN, "image reference not exist")
		return
	}
	digest, err := GenDigest("sha256", bytes.NewBuffer([]byte(refItem.Content)))
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_INVALID, err.Error())
		return
	}
	c.Header("Content-Type", refItem.ContentType)
	c.Header("Content-Length", fmt.Sprintf("%d", len(refItem.Content)))
	c.Header("Docker-Content-Digest", digest)
	c.Status(200)
}

func ProcessLoadManifest(c *gin.Context) {
	groupName := c.Param("group")
	imageName := c.Param("name")
	imageRef := c.Param("ref")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}

	//检查用户是否可以pull
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PullSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow pull")
		return
	}

	refItem, err := storage.ImageReferenceDao.Get(c.Request.Context(), groupName, imageName, imageRef)
	if err != nil {
		SendError(c, 404, ERROR_CODE_NAME_UNKNOWN, "image reference not exist")
		return
	}
	digest, err := GenDigest("sha256", bytes.NewBuffer([]byte(refItem.Content)))
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_INVALID, err.Error())
		return
	}
	c.Header("Docker-Content-Digest", digest)
	c.Data(200, refItem.ContentType, []byte(refItem.Content))
}

func ProcessRemoveManifest(c *gin.Context) {
	groupName := c.Param("group")
	imageName := c.Param("name")
	imageRef := c.Param("ref")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}
	//检查用户是否可以push
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PushSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow remove")
		return
	}
	//删除reference
	exist, err := storage.ImageReferenceDao.Exist(c.Request.Context(), groupName, imageName, imageRef)
	if err != nil {
		SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
		return
	}
	if !exist {
		SendError(c, 404, ERROR_CODE_MANIFEST_UNKNOWN, "manifest not exist")
		return
	}
	//删除reference
	err = storage.ImageReferenceDao.Remove(c.Request.Context(), groupName, imageName, imageRef)
	if err != nil {
		SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
		return
	}
	//调整镜像reference计数
	count, err := storage.ImageReferenceDao.Count(c.Request.Context(), groupName, imageName, false)
	if err != nil {
		SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
		return
	}
	imageInfo, err := storage.ImageInfoDao.Get(c.Request.Context(), groupName, imageName)
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_UNKNOWN, err.Error())
		return
	}
	imageInfo.ReferenceCount = count
	imageInfo.UpdateTime = time.Now().UnixMilli()
	err = storage.ImageInfoDao.Update(c.Request.Context(), imageInfo)
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_UNKNOWN, err.Error())
		return
	}
	c.Status(200)
}

func ProcessStoreManifest(c *gin.Context) {
	groupName := c.Param("group")
	imageName := c.Param("name")
	imageRef := c.Param("ref")
	contentType := c.GetHeader("Content-Type")

	groupInfo, err := storage.ImageGroupDao.Get(c.Request.Context(), groupName)
	if err != nil {
		SendError(c, 403, ERROR_CODE_DENIED, "image group not exist")
		return
	}
	//检查用户是否可以push
	secName := GetSecretUsername(c)
	allow := false
	for _, secretName := range groupInfo.PermInfo.PushSecretNameList {
		if secName == secretName {
			allow = true
			break
		}
	}
	if !allow {
		SendError(c, 403, ERROR_CODE_DENIED, "not allow push")
		return
	}

	//创建镜像信息
	err = CreateImageInfo(c.Request.Context(), groupName, imageName)
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_UNKNOWN, err.Error())
		return
	}

	manifestData, err := io.ReadAll(c.Request.Body)
	if err != nil {
		SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
		return
	}

	digest, err := GenDigest("sha256", bytes.NewBuffer(manifestData))
	if err != nil {
		SendError(c, 400, ERROR_CODE_DIGEST_INVALID, err.Error())
		return
	}
	//保存tag
	refItem, err := storage.ImageReferenceDao.Get(c.Request.Context(), groupName, imageName, imageRef)
	if err == nil { //对应Tag存在
		oldDigest := refItem.Digest
		refItem.ContentType = contentType
		refItem.Content = string(manifestData)
		refItem.Digest = digest
		err = storage.ImageReferenceDao.Update(c.Request.Context(), refItem)
		if err != nil {
			SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
			return
		}
		if oldDigest != digest {
			sendChange(c.Request.Context(), groupName, imageName, imageRef, oldDigest, imageRef, digest) //skip error check
		}
	} else { //对应标签不存在
		imageRefItemList, err := storage.ImageReferenceDao.List(c.Request.Context(), groupName, imageName, false)
		if err != nil {
			SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
			return
		}
		oldTag := ""
		oldDigest := ""
		if len(imageRefItemList) > 0 {
			oldTag = imageRefItemList[0].Reference
			oldDigest = imageRefItemList[0].Digest
		}
		err = storage.ImageReferenceDao.Insert(c.Request.Context(), &core.ImageReferenceInfo{
			GroupName:   groupName,
			ImageName:   imageName,
			Reference:   imageRef,
			TimeStamp:   time.Now().UnixMilli(),
			ContentType: contentType,
			Content:     string(manifestData),
			Digest:      digest,
		})
		if err != nil {
			SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
			return
		}
		sendChange(c.Request.Context(), groupName, imageName, oldTag, oldDigest, imageRef, digest) //skip error check
	}
	//保存digest
	exist, err := storage.ImageReferenceDao.Exist(c.Request.Context(), groupName, imageName, digest)
	if err != nil {
		SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
		return
	}
	if !exist {
		err = storage.ImageReferenceDao.Insert(c.Request.Context(), &core.ImageReferenceInfo{
			GroupName:   groupName,
			ImageName:   imageName,
			Reference:   digest,
			TimeStamp:   time.Now().UnixMilli(),
			ContentType: contentType,
			Content:     string(manifestData),
			Digest:      digest,
		})
		if err != nil {
			SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
			return
		}
	}
	//调整镜像reference计数
	count, err := storage.ImageReferenceDao.Count(c.Request.Context(), groupName, imageName, false)
	if err != nil {
		SendError(c, 400, ERROR_CODE_MANIFEST_INVALID, err.Error())
		return
	}
	imageInfo, err := storage.ImageInfoDao.Get(c.Request.Context(), groupName, imageName)
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_UNKNOWN, err.Error())
		return
	}
	imageInfo.ReferenceCount = count
	imageInfo.UpdateTime = time.Now().UnixMilli()
	err = storage.ImageInfoDao.Update(c.Request.Context(), imageInfo)
	if err != nil {
		SendError(c, 400, ERROR_CODE_NAME_UNKNOWN, err.Error())
		return
	}

	c.Header("Location", fmt.Sprintf("/v2/%s/%s/manifests/%s", url.PathEscape(groupName), url.PathEscape(imageName), url.PathEscape(imageRef)))
	c.Header("Docker-Content-Digest", digest)
	c.Status(201)
}

func sendChange(ctx context.Context, groupName, imageName, oldTag, oldDigest, newTag, newDigest string) error {
	//读取配置
	cfg, err := config.ReadServerConfigFromCache()
	if err != nil {
		return err
	}
	//列出相关watch
	fullImageName := fmt.Sprintf("%s/%s", groupName, imageName)

	watchItemList, err := storage.WatchInfoDao.List(ctx)
	if err != nil {
		return nil
	}
	matchWatchItemList := []*watch_api.WatchInfo{}
	for _, watchItem := range watchItemList {
		for _, cmpFullImageName := range watchItem.FullImageNameList {
			if cmpFullImageName == fullImageName {
				matchWatchItemList = append(matchWatchItemList, watchItem)
				break
			}
		}
	}

	for _, watchItem := range matchWatchItemList {
		changeInfo := &watch_api.ChangeInfo{
			ChangeId:         uuid.NewString(),
			WatchId:          watchItem.WatchId,
			TimeStamp:        time.Now().UnixMilli(),
			ChangeFromUrl:    fmt.Sprintf("%s/%s/%s:%s", cfg.Domain, groupName, imageName, oldTag),
			ChangeToUrl:      fmt.Sprintf("%s/%s/%s:%s", cfg.Domain, groupName, imageName, newTag),
			ChangeFromDigest: oldDigest,
			ChangeToDigest:   newDigest,
			UnChangeUrlList:  []string{},
		}
		for _, cmpFullImageName := range watchItem.FullImageNameList {
			if cmpFullImageName != fullImageName {
				parts := strings.Split(cmpFullImageName, "/")
				if len(parts) != 2 {
					continue
				}
				imageRefItemList, err := storage.ImageReferenceDao.List(ctx, parts[0], parts[1], false)
				if err != nil {
					return err
				}
				if len(imageRefItemList) > 0 {
					unChangeUrl := fmt.Sprintf("%s/%s/%s:%s", cfg.Domain, imageRefItemList[0].GroupName, imageRefItemList[0].ImageName, imageRefItemList[0].Reference)
					changeInfo.UnChangeUrlList = append(changeInfo.UnChangeUrlList, unChangeUrl)
				}
			}
		}
		//保存changeInfo
		err = storage.ChangeDao.Insert(ctx, changeInfo)
		if err != nil {
			return err
		}
		count, err := storage.ChangeDao.Count(ctx, watchItem.WatchId)
		if err != nil {
			return err
		}
		watchItem.ChangeCount = count
		err = storage.WatchInfoDao.Update(ctx, watchItem)
		if err != nil {
			return err
		}
		if !watchItem.Enable {
			continue
		}
		go func(watchId, changeId string) {
			storage.ChangeNoticeDao.SendChange(watchId, changeId, "")
			time.Sleep(30 * time.Second)
			storage.ChangeNoticeDao.SendChange(watchId, changeId, "")
			time.Sleep(30 * time.Second)
			storage.ChangeNoticeDao.SendChange(watchId, changeId, "")
		}(changeInfo.WatchId, changeInfo.ChangeId)
	}

	return nil
}
